#pragma once

template <typename T>
class Memory {
 public:
  //
  // This class guarantees that the returned allocated memory
  // will always be zeroed
  //
  explicit Memory(const char* Reason = "Memory:???") {
    m_Ptr = nullptr;
    m_Size = 0;
#ifdef ENABLE_MEM_TRACE
    m_Reason = Reason;
#endif  // ENABLE_MEM_TRACE
  }

  explicit Memory(size_t Size, const char* Reason = "Memory:???") {
    m_Ptr = reinterpret_cast<T>(emalloc(Size, Reason));
    m_Size = Size;
#ifdef ENABLE_MEM_TRACE
    m_Reason = Reason;
#endif  // ENABLE_MEM_TRACE

    memset(m_Ptr, 0, Size);
  }

  ~Memory() {
    if (m_Ptr)
#ifdef ENABLE_MEM_TRACE
      efree(m_Ptr, m_Reason);
#else
      efree(m_Ptr);
#endif  // ENABLE_MEM_TRACE
  }

  T realloc(size_t Size, const char* Reason = "Memory:???") {
    m_Ptr = reinterpret_cast<T>(erealloc(m_Ptr, Size, Reason));
    m_Size = Size;
#ifdef ENABLE_MEM_TRACE
    m_Reason = Reason;
#endif  // ENABLE_MEM_TRACE

    return (T)memset(m_Ptr, 0, m_Size);
  }

  size_t size() const { return m_Size; }

  T operator()() { return m_Ptr; }

 private:
  T m_Ptr;
  size_t m_Size;
#ifdef ENABLE_MEM_TRACE
  const char* m_Reason;
#endif  // ENABLE_MEM_TRACE
};